﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using Microsoft.Coding4Fun.TweetCraft.Twitter;
using Microsoft.Coding4Fun.TweetCraft.Wow;

using Moq;

using NUnit.Framework;

namespace Microsoft.Coding4Fun.TweetCraft.Tests
{
    [TestFixture]
    public class TweetCraftTest
    {
        private Mock<ITwitterClient> twitterMock;
        private Mock<ITwitterBackgroundService> backgroundServiceMock;
        private Mock<ITwitterUserPictureService> userPictureServiceMock;
        private Mock<ITwitterDispatcherService> dispatcherServiceMock;

        private Mock<WorldOfWarcraft> worldOfWarcraftMock;
        private Mock<ISavedVariablesChannelFactory> channelFactoryMock;
        private Mock<ISavedVariablesChannel> channelMock;
        private Mock<IReportsError> errorReportingChannelMock;
        private ITweetCraft tweetCraft;

        [SetUp]
        public void SetUp()
        {
            // Initialize service mocks
            twitterMock = new Mock<ITwitterClient>();
            backgroundServiceMock = new Mock<ITwitterBackgroundService>();
            userPictureServiceMock = new Mock<ITwitterUserPictureService>();
            dispatcherServiceMock = new Mock<ITwitterDispatcherService>();

            // Create World of Warcraft mock based on default implementation
            worldOfWarcraftMock = new Mock<WorldOfWarcraft>();

            // Create channel factory mock and channel mock
            channelFactoryMock = new Mock<ISavedVariablesChannelFactory>();
            channelMock = new Mock<ISavedVariablesChannel>();

            // Add IReportsError interface to channel
            errorReportingChannelMock = channelMock.As<IReportsError>();

            // Set up channel factory mock to return the channel mock
            channelFactoryMock.Setup(channelFactory => channelFactory.CanCreateChannel())
                .Returns(true);
            channelFactoryMock.Setup(channelFactory => channelFactory.CreateChannel())
                .Returns(channelMock.Object);

            // Override World of Warcraft behavior
            worldOfWarcraftMock.Setup(worldOfWarcraft => worldOfWarcraft.InstallPath)
                .Returns(Environment.CurrentDirectory);

            // Initialize TweetCraft instance
            tweetCraft = new TweetCraft(twitterMock.Object, worldOfWarcraftMock.Object, channelFactoryMock.Object, backgroundServiceMock.Object, userPictureServiceMock.Object, dispatcherServiceMock.Object);
        }

        [TearDown]
        public void TearDown()
        {
            if (tweetCraft != null)
                tweetCraft.Dispose();
        }

        [Test]
        public void RunTest()
        {
            tweetCraft.Run();

            Assert.IsTrue(tweetCraft.IsRunning);
        }

        // ISSUE: Raising the IReportsError event on a mock that primarily implements another interface does not work (possibly bug in Moq)
        // [Test]
        public void ErrorTest()
        {
            // Create an auto reset event to signal that the Error event was raised
            var errorEvent = new AutoResetEvent(false);

            // Subscribe to Error event and assert arguments
            tweetCraft.Error += delegate(object sender, ErrorEventArgs e)
                                {
                                    // Assert that an exception has been passed as event arguments
                                    Assert.IsNotNull(e.Exception);

                                    // Signal that the event was raised
                                    errorEvent.Set();
                                };

            // Run TweetCraft
            tweetCraft.Run();

            // Raise Error event on the channel
            errorReportingChannelMock.Raise(channel => channel.Error += null);

            // Wait for the Error event to be raised
            if (!errorEvent.WaitOne(TimeSpan.FromSeconds(1)))
                Assert.Fail("Timed out while waiting for Error event.");
        }

        [Test]
        public void ChannelUpdatedQueueTweetsTest()
        {
            const string message = "Hello from WoW!";

            // Set up dispatcher service to verify a call to QueueTweets with a single tweet
            dispatcherServiceMock.Setup(dispatcherService => dispatcherService.QueueTweets(It.IsAny<Tweet[]>()))
                .Callback(delegate(IEnumerable<Tweet> tweets)
                          {
                              // Assert that a single tweet was queried
                              Assert.AreEqual(1, tweets.Count());

                              // Assert that the message is correct
                              Assert.AreEqual(message, tweets.First().Message);
                          })
                .Verifiable();

            // Set up IsAvailable to return true for 'outgoingTweets'
            channelMock.Setup(channel => channel.IsAvailable("outgoingTweets"))
                .Returns(true);

            // Set up ReadValue to return a single tweet for 'outgoingTweets'
            channelMock.Setup(channel => channel.ReadValue<Tweet[]>("outgoingTweets"))
                .Returns(new[]
                         {
                             new Tweet
                             {
                                 Message = message
                             }
                         });

            // Run TweetCraft
            tweetCraft.Run();

            // Raise ChannelUpdated event
            channelMock.Raise(channel => channel.ChannelUpdated += null, EventArgs.Empty);

            // Verify expectations
            dispatcherServiceMock.VerifyAll();
        }
    }
}
